#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <linux/if_ether.h>

#include "wsc_supplicant.h"
#include "l2_packet.h"
//#include "eloop.h"

extern char *optarg;

extern void InitStateMachine(u8 *own_addr, u8 *ap_addr, u8 method, u8 *pin);
extern int wpa_supplicant_eapol_send(struct wsc_drv *drv, int type, const u8 *buf,
				    size_t len);
extern int wsc_supplicant_connect(struct wsc_drv *wsc_drv);
extern int wsc_supplicant_run(struct wsc_drv *wsc_drv);
extern int wps_driver_zydas_wps_filter(struct wsc_drv *drv, int enable);
extern int zydas_set_param(struct wsc_drv *drv, int op, int arg, int show_err);

struct wsc_drv *g_wsc = NULL;

void usage(void)
{
    printf("Usage: wsc_supplicant -B -i <ifname> -b <bridge> -s <ssid> -p <PIN>\n");
    exit(0);
}

void sig_handler(int signo)
{
    if (signo != SIGINT && signo != SIGTERM && signo != SIGHUP)
       return;
    if(g_wsc != NULL)
    {
        wps_driver_zydas_wps_filter(g_wsc,0);
        if (zydas_set_param(g_wsc, 1, 0, 1) < 0)
            printf("Set Roamming failed\n");
    }
    exit(0);
    //wsc_supplicant_destroy(g_wsc);

}

int ValidateChecksum( unsigned long int PIN )
{
    unsigned long int accum = 0;
	accum += 3 * ((PIN / 10000000) % 10); 
	accum += 1 * ((PIN / 1000000) % 10); 
	accum += 3 * ((PIN / 100000) % 10); 
	accum += 1 * ((PIN / 10000) % 10); 
	accum += 3 * ((PIN / 1000) % 10); 
	accum += 1 * ((PIN / 100) % 10); 
	accum += 3 * ((PIN / 10) % 10); 
	accum += 1 * ((PIN / 1) % 10); 
	
    return (0 == (accum % 10));
} // ValidateChecksum

unsigned long int GeneratePIN()
{
    int i;
    unsigned long int accum = 0;
    unsigned long int pinNum = 0;

    srand((unsigned long int)time(0));
    for(i = 0; i < 7; i++)
    {
        if (i == 0)
            pinNum += 1 + (unsigned long int)(9.0*rand()/(RAND_MAX+1.0));
        else
            pinNum += (unsigned long int)(10.0*rand()/(RAND_MAX+1.0));
        pinNum *= 10;
    }
	accum += 3 * ((pinNum / 10000000) % 10); 
	accum += 1 * ((pinNum / 1000000) % 10); 
	accum += 3 * ((pinNum / 100000) % 10); 
	accum += 1 * ((pinNum / 10000) % 10); 
	accum += 3 * ((pinNum / 1000) % 10); 
	accum += 1 * ((pinNum / 100) % 10); 
	accum += 3 * ((pinNum / 10) % 10); 
    pinNum += ((10 - (accum % 10)) % 10);
    
    return pinNum;
}

int wsc_parse_config(struct wsc_drv *wsc_drv, char *config)
{
    FILE *fp;
    char buf[80];
    char methodBuf[10], aliveBuf[10], daemonBuf[10], delayBuf[10], policyBuf[10], serviceBuf[10];

    fp = fopen(config, "r");

    if (fp == NULL)
    {
        printf("Can't open file: %s for read\n", config);
        return -1;
    }

    while(fgets(buf, 80, fp) != NULL)
    {
        if (buf[0] == '\r' || buf[0] == '#')
        {
            continue;	
        }
        else
        {
            if(strstr(buf,"CONFIG_MANUFACTURER"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->CONFIG_MANUFACTURER, strtok(NULL, "\r\n"), 32);
                printf("CONFIG_MANUFACTURER:%s\n", wsc_drv->CONFIG_MANUFACTURER);
            }
            else if(strstr(buf,"CONFIG_MODEL_NAME"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->CONFIG_MODEL_NAME, strtok(NULL, "\r\n"), 32);
                printf("CONFIG_MODEL_NAME:%s\n", wsc_drv->CONFIG_MODEL_NAME);
            }
            else if(strstr(buf,"CONFIG_MODEL_NUMBER"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->CONFIG_MODEL_NUMBER, strtok(NULL, "\r\n"), 32);
                printf("CONFIG_MODEL_NUMBER:%s\n", wsc_drv->CONFIG_MODEL_NUMBER);
            }
            else if(strstr(buf,"CONFIG_DEVICE_NAME"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->CONFIG_DEVICE_NAME, strtok(NULL, "\r\n"), 32);
                printf("CONFIG_DEVICE_NAME:%s\n", wsc_drv->CONFIG_DEVICE_NAME);
            }
            else if(strstr(buf,"CONFIG_DEVICE_UUID"))
            {
                int i;
                int d[16];
                strtok(buf, "=");
                sscanf(strtok(NULL,"\r\n"),"%x,%x,%x,%x, %x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x",
                       d,d+1,d+2,d+3,d+4,d+5,d+6,d+7,d+8,d+9,d+10,d+11,d+12,d+13,d+14,d+15);
                printf("CONFIG_DEVICE_UUID=");
                for(i=0;i<16;i++)
                {
                    wsc_drv->CONFIG_DEVICE_UUID[i] = d[i] & 0xFF;
                    printf("%02x ", d[i] & 0xFF);
                }    
                printf("\n");
            }
            else if(strstr(buf,"CONFIG_PRIMARY_DEVICE_TYPE"))
            {
                int i;
                int d[8];
                strtok(buf, "=");
                sscanf(strtok(NULL,"\r\n"),"%x,%x,%x,%x, %x,%x,%x,%x",
                       d,d+1,d+2,d+3,d+4,d+5,d+6,d+7);
                printf("CONFIG_PRIMARY_DEVICE_TYPE=");
                for(i=0;i<8;i++)
                {
                    wsc_drv->CONFIG_PRIMARY_DEVICE_TYPE[i] = d[i] & 0xFF;
                    printf("%02x ", d[i] & 0xFF);
                }    
                printf("\n");
            }
            else if(strstr(buf,"CONFIG_RFBands"))
            {
                int d[1];
                strtok(buf, "=");
                sscanf(strtok(NULL,"\r\n"),"%d", d);
                printf("CONFIG_RFBands=%d\n",d[0]);
                wsc_drv->CONFIG_RFBANDS = d[0];
            }

	    else if (strstr(buf, "wds_ap"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->wds_ap_ifnameBody, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->wds_ap_ifname = wsc_drv->wds_ap_ifnameBody;
                printf("wds_ap: %s\n", wsc_drv->wds_ap_ifnameBody);
            }
            else if (strstr(buf, "wds_sta"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->ifnameBody, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->ifname = wsc_drv->ifnameBody;
                printf("wds_sta: %s\n", wsc_drv->ifnameBody);
            }
            else if (strstr(buf, "bridge"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->bridge_ifnameBody, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->bridge_ifname = wsc_drv->bridge_ifnameBody;
                printf("bridge: %s\n", wsc_drv->bridge_ifnameBody);
            }
            /*else if (strstr(buf, "ssid"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->ssidBody, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->ssid = wsc_drv->ssidBody;
                wsc_drv->ssid_len = strlen(wsc_drv->ssidBody);
                printf("ssid: %s\n", wsc_drv->ssidBody);
            }*/
            else if (strstr(buf, "method"))
            {
                strtok(buf, "=");
                
                strncpy(methodBuf, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->method = atoi(methodBuf);
                printf("method: %d\n", wsc_drv->method);
            }
            else if (strstr(buf, "alive"))
            {
                strtok(buf, "=");
                
                strncpy(aliveBuf, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->alive = atoi(aliveBuf);
                printf("alive: %d\n", wsc_drv->alive);
            }
            else if (strstr(buf, "nodaemon"))
            {
                strtok(buf, "=");
                
                strncpy(daemonBuf, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->nodaemon = atoi(daemonBuf);
                printf("nodaemon: %d\n", wsc_drv->nodaemon);
            }
            else if (strstr(buf, "sup_delay"))
            {
                strtok(buf, "=");
                
                strncpy(delayBuf, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->sup_delay = atoi(delayBuf);
                printf("wpa_supplicant delay: %d\n", wsc_drv->sup_delay);
            }
            else if (strstr(buf, "policy"))
            {
                strtok(buf, "=");
                
                strncpy(policyBuf, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->policy = atoi(policyBuf);
                if (wsc_drv->policy)
                    printf("AP decision policy is : detect pbc pattern\n");
                else
                    printf("AP decision policy is : recursive scan\n");
            }
            else if (strstr(buf, "service"))
            {
                strtok(buf, "=");
                
                strncpy(serviceBuf, strtok(NULL, "\r\n"), IFNAMSIZ);
                wsc_drv->serviceType = atoi(serviceBuf);
                switch (wsc_drv->serviceType)
                {
                    case 0x1: printf("AP service type is : public\n");
                            break;
                    case 0x2: printf("AP service type is : data\n");
                            break;
                    case 0x4: printf("AP service type is : voice\n");
                            break;
                    case 0x8: printf("AP service type is : video\n");
                            break;
                    default:printf("Unknown AP service type, set to be public\n");
                            wsc_drv->serviceType = 1;
                            break;
                }
            }
            else if (strstr(buf, "pin"))
            {
                strtok(buf, "=");
                strncpy(wsc_drv->pinBody, strtok(NULL, "\r\n"), IFNAMSIZ);
                if (ValidateChecksum(atol(wsc_drv->pinBody)))
                {
                    wsc_drv->pin = wsc_drv->pinBody;
                    printf("pin : %s\n", wsc_drv->pinBody);
                }
                else
                {
                    wsc_drv->pin = NULL;
                    printf("pin : %s invalid, checksum error, use push button method.\n", wsc_drv->pinBody);
                }
            }
        }
    }

    fclose(fp);
    return 0;
}

int main(int argc, char* argv[])
{
    extern void randomlibAddEarly(unsigned noise);
    extern void simpleconfigSessionKeyGen1Pre(void);
    //int daemonize = 0;//, debug = 0;
    //int c;
    int ret;
    unsigned long pinNum = 0;
    struct wsc_drv *wsc_drv = NULL;

    wsc_drv = calloc(sizeof(struct wsc_drv), 1);

    if (wsc_drv == NULL)
    {
        printf("Can't allocate memory for wsc driver\n");
        exit(0);
    }

    if (argc != 3)
    {
        printf("Usage: wps_enrollee [none | pin | pbc] <config file>\n");
        printf("       none : use real push button\n");
        printf("       pin  : use pin code in <config file>\n");
        printf("              or random generate pin code\n");
        printf("       pbc  : start with push button pressed\n");
        exit(0);
    }

    g_wsc = wsc_drv;

    wsc_drv->wds_ap_ifname = NULL;
    wsc_drv->l2_br = NULL;
    wsc_drv->bridge_ifname = NULL;
    wsc_drv->timeout = NULL;
    wsc_drv->ssid = NULL;
    wsc_drv->pin = NULL;
    wsc_drv->pushButtonPress = 0;
    wsc_drv->wpa_supplicant_pid = 0;
    wsc_drv->hostapd_pid = 0;
    wsc_drv->wsccmd_pid = 0;
    wsc_drv->method = 0;
    wsc_drv->alive = 0;
    wsc_drv->nodaemon = 0;
    wsc_drv->sup_delay = 3;
    wsc_drv->policy = 0;
    wsc_drv->blockAPNum = 0;
    wsc_drv->resendTime = 0;
    
#if 0
    /* Parse the arguments */
    for (;;) {
        c = getopt(argc, argv, "Bdhi:b:s:p:");
        if (c < 0)
            break;

        switch (c) {
        case 'b':
            printf("bridge interface: %s\n", optarg);
            wsc_drv->bridge_ifname = optarg;
            break;
        case 'i':
            printf("network interface: %s\n", optarg);
            wsc_drv->ifname = optarg;
            break;
        case 's':
            printf("Root AP ssid: %s\n", optarg);
            wsc_drv->ssid = optarg;
            wsc_drv->ssid_len = strlen(optarg);
            break;
        case 'p':
            printf("Pin: %s\n", optarg);
            wsc_drv->pin = optarg;
            break;
		case 'B':
			daemonize++;
			break;
        case 'h':
            usage();
            break;
        case 'd':
            debug++;
            break;

        default:
            usage();
            break;
        }
    }
#endif
    wsc_parse_config(wsc_drv, argv[2]);
    
    if (strstr(argv[1], "pin"))
    {
        wsc_drv->startOP = 1;
        if (wsc_drv->pin == NULL)
        {
            pinNum = GeneratePIN();
            sprintf(wsc_drv->pinBody, "%08ld", pinNum);
            printf("pin code [ %s ] generated, please key in this to AP\n", wsc_drv->pinBody);
            wsc_drv->pin = wsc_drv->pinBody;
        }
    }
    else if (strstr(argv[1], "pbc"))
    {
        wsc_drv->startOP = 2;
        wsc_drv->pin = NULL;
    }
    else
        wsc_drv->startOP = 0;
    
    //wsc_drv->debug = debug;
    wsc_drv->debug = WSC_DBG_CONNECT;

    /* Parameters check */
    if (wsc_drv->ifname == NULL)
    {
        printf("interface can't be NULL\n");
        usage();
        exit(0);
    }

	if (wsc_drv->nodaemon == 0) {
	    if (daemon(0, 0)) {
	    	perror("daemon");
	    	return 0;
	    }
    }

    /* Do the initialization for Wifi simple configuration */
    ret = wsc_supplicant_init(wsc_drv);

    if (ret != 0)
    {
        printf("wsc_supplicant_init fail\n");
        exit(0);
    }

    /* Register signal function */
    signal(SIGINT, sig_handler);
    signal(SIGTERM, sig_handler);
    signal(SIGHUP, sig_handler);

    randomlibAddEarly(time(NULL));
    simpleconfigSessionKeyGen1Pre();
    wsc_supplicant_run(wsc_drv);

    return 0;
}

